<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Module 的加载实现 | Keith&#39;s blog</title>
    <meta name="generator" content="VuePress 1.8.0">
    <link rel="icon" href="/img/favicon.png">
    <script data-ad-client="ca-pub-7828333725993554" async="async" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
    <meta name="description" content="web前端技术博客,简洁至上,专注web前端学习与总结。JavaScript,js,ES6,TypeScript,vue,python,css3,html5,Node,git,github等技术文章。">
    <meta name="keywords" content="前端博客,个人技术博客,前端,前端开发,前端框架,web前端,前端面试题,技术文档,学习,面试,JavaScript,js,ES6,TypeScript,vue,python,css3,html5,Node,git,github,markdown">
    <meta name="baidu-site-verification" content="7F55weZDDc">
    <meta name="theme-color" content="#11a8cd">
    <link rel="preload" href="/assets/css/0.styles.95a28cfa.css" as="style"><link rel="preload" href="/assets/js/app.ae5bf724.js" as="script"><link rel="preload" href="/assets/js/2.72b09a26.js" as="script"><link rel="preload" href="/assets/js/3.96877024.js" as="script"><link rel="preload" href="/assets/js/107.7ca5e73a.js" as="script"><link rel="prefetch" href="/assets/js/10.3e431465.js"><link rel="prefetch" href="/assets/js/100.c7b84f24.js"><link rel="prefetch" href="/assets/js/101.e5a7e6f5.js"><link rel="prefetch" href="/assets/js/102.85992487.js"><link rel="prefetch" href="/assets/js/103.3f187c57.js"><link rel="prefetch" href="/assets/js/104.103d7471.js"><link rel="prefetch" href="/assets/js/105.df0063c0.js"><link rel="prefetch" href="/assets/js/106.d3f285ee.js"><link rel="prefetch" href="/assets/js/108.b5e1495a.js"><link rel="prefetch" href="/assets/js/109.12b39840.js"><link rel="prefetch" href="/assets/js/11.c0c2ac1e.js"><link rel="prefetch" href="/assets/js/110.2996b4eb.js"><link rel="prefetch" href="/assets/js/111.752c7e87.js"><link rel="prefetch" href="/assets/js/112.7c01a356.js"><link rel="prefetch" href="/assets/js/113.240fbeb5.js"><link rel="prefetch" href="/assets/js/114.502fae11.js"><link rel="prefetch" href="/assets/js/115.28ee11f8.js"><link rel="prefetch" href="/assets/js/116.a3f220c8.js"><link rel="prefetch" href="/assets/js/117.293dbd7c.js"><link rel="prefetch" href="/assets/js/118.7fe06c79.js"><link rel="prefetch" href="/assets/js/119.eb539041.js"><link rel="prefetch" href="/assets/js/12.0ea0beb2.js"><link rel="prefetch" href="/assets/js/120.8502bd78.js"><link rel="prefetch" href="/assets/js/121.696d358f.js"><link rel="prefetch" href="/assets/js/122.a629ef96.js"><link rel="prefetch" href="/assets/js/123.bc97cb45.js"><link rel="prefetch" href="/assets/js/124.e9dbc4a1.js"><link rel="prefetch" href="/assets/js/125.3372248a.js"><link rel="prefetch" href="/assets/js/126.58bf8057.js"><link rel="prefetch" href="/assets/js/127.532c80ac.js"><link rel="prefetch" href="/assets/js/128.a8a9dd63.js"><link rel="prefetch" href="/assets/js/129.952e36b7.js"><link rel="prefetch" href="/assets/js/13.81b38e3a.js"><link rel="prefetch" href="/assets/js/130.3f453493.js"><link rel="prefetch" href="/assets/js/131.15bb01bf.js"><link rel="prefetch" href="/assets/js/132.5e501da7.js"><link rel="prefetch" href="/assets/js/133.fbd296a8.js"><link rel="prefetch" href="/assets/js/134.a455c276.js"><link rel="prefetch" href="/assets/js/135.50985645.js"><link rel="prefetch" href="/assets/js/136.dd156bdc.js"><link rel="prefetch" href="/assets/js/137.91d77519.js"><link rel="prefetch" href="/assets/js/138.af96bbc9.js"><link rel="prefetch" href="/assets/js/139.ff60e390.js"><link rel="prefetch" href="/assets/js/14.13ff5f93.js"><link rel="prefetch" href="/assets/js/140.51ef13cd.js"><link rel="prefetch" href="/assets/js/141.2c36f0bb.js"><link rel="prefetch" href="/assets/js/142.745a5a9d.js"><link rel="prefetch" href="/assets/js/143.ef12ce5a.js"><link rel="prefetch" href="/assets/js/144.d53b8a10.js"><link rel="prefetch" href="/assets/js/145.01c80734.js"><link rel="prefetch" href="/assets/js/146.ff1abf8c.js"><link rel="prefetch" href="/assets/js/147.b334a956.js"><link rel="prefetch" href="/assets/js/148.82d1c504.js"><link rel="prefetch" href="/assets/js/149.4872ca41.js"><link rel="prefetch" href="/assets/js/15.ba6da9d5.js"><link rel="prefetch" href="/assets/js/150.eaa1b09e.js"><link rel="prefetch" href="/assets/js/151.b9deeab6.js"><link rel="prefetch" href="/assets/js/152.8914ff06.js"><link rel="prefetch" href="/assets/js/153.1c968910.js"><link rel="prefetch" href="/assets/js/154.ac2ac8e9.js"><link rel="prefetch" href="/assets/js/155.a41ac4d1.js"><link rel="prefetch" href="/assets/js/156.07ec7daa.js"><link rel="prefetch" href="/assets/js/157.d0cfaeb2.js"><link rel="prefetch" href="/assets/js/158.4a3844de.js"><link rel="prefetch" href="/assets/js/159.cd2739bf.js"><link rel="prefetch" href="/assets/js/16.bd936cd0.js"><link rel="prefetch" href="/assets/js/160.8ea34b7b.js"><link rel="prefetch" href="/assets/js/161.4bb2ece8.js"><link rel="prefetch" href="/assets/js/162.a4b2690b.js"><link rel="prefetch" href="/assets/js/163.85acbc57.js"><link rel="prefetch" href="/assets/js/164.acce92e2.js"><link rel="prefetch" href="/assets/js/165.08b51ce6.js"><link rel="prefetch" href="/assets/js/166.7ad057e5.js"><link rel="prefetch" href="/assets/js/167.2d35a768.js"><link rel="prefetch" href="/assets/js/168.b7790709.js"><link rel="prefetch" href="/assets/js/169.11b5000c.js"><link rel="prefetch" href="/assets/js/17.1d7263eb.js"><link rel="prefetch" href="/assets/js/170.d93daf1f.js"><link rel="prefetch" href="/assets/js/171.14a45c02.js"><link rel="prefetch" href="/assets/js/172.c62eb70d.js"><link rel="prefetch" href="/assets/js/173.9dd4cb5e.js"><link rel="prefetch" href="/assets/js/174.deca226f.js"><link rel="prefetch" href="/assets/js/175.eef20df3.js"><link rel="prefetch" href="/assets/js/176.c09f5a25.js"><link rel="prefetch" href="/assets/js/177.99e3a6a8.js"><link rel="prefetch" href="/assets/js/178.22bd595d.js"><link rel="prefetch" href="/assets/js/179.ef63471d.js"><link rel="prefetch" href="/assets/js/18.9412ae71.js"><link rel="prefetch" href="/assets/js/180.306daca4.js"><link rel="prefetch" href="/assets/js/181.9f8eae15.js"><link rel="prefetch" href="/assets/js/182.36cd1ffa.js"><link rel="prefetch" href="/assets/js/183.0195ae23.js"><link rel="prefetch" href="/assets/js/184.61363002.js"><link rel="prefetch" href="/assets/js/185.d01ed7e5.js"><link rel="prefetch" href="/assets/js/186.915e1946.js"><link rel="prefetch" href="/assets/js/187.392993c9.js"><link rel="prefetch" href="/assets/js/188.afcb3c55.js"><link rel="prefetch" href="/assets/js/189.99313768.js"><link rel="prefetch" href="/assets/js/19.17a0f886.js"><link rel="prefetch" href="/assets/js/190.3740a1c8.js"><link rel="prefetch" href="/assets/js/191.a8cebbb8.js"><link rel="prefetch" href="/assets/js/192.a4e4b0eb.js"><link rel="prefetch" href="/assets/js/193.f87069af.js"><link rel="prefetch" href="/assets/js/194.a83aa1ad.js"><link rel="prefetch" href="/assets/js/195.6cccc323.js"><link rel="prefetch" href="/assets/js/196.0ca74fc2.js"><link rel="prefetch" href="/assets/js/197.e57085c4.js"><link rel="prefetch" href="/assets/js/198.9d8c4195.js"><link rel="prefetch" href="/assets/js/199.e34f6d16.js"><link rel="prefetch" href="/assets/js/20.751f715d.js"><link rel="prefetch" href="/assets/js/200.4f0c1baf.js"><link rel="prefetch" href="/assets/js/201.92b5153f.js"><link rel="prefetch" href="/assets/js/202.9f5b1117.js"><link rel="prefetch" href="/assets/js/203.84c5aa3f.js"><link rel="prefetch" href="/assets/js/204.950ec43f.js"><link rel="prefetch" href="/assets/js/205.3caa1fd5.js"><link rel="prefetch" href="/assets/js/206.c5d73eeb.js"><link rel="prefetch" href="/assets/js/207.f00a7726.js"><link rel="prefetch" href="/assets/js/208.49394867.js"><link rel="prefetch" href="/assets/js/209.f891c646.js"><link rel="prefetch" href="/assets/js/21.7e3b1dd6.js"><link rel="prefetch" href="/assets/js/210.9b3ce6fe.js"><link rel="prefetch" href="/assets/js/211.22cbc362.js"><link rel="prefetch" href="/assets/js/212.5380a60a.js"><link rel="prefetch" href="/assets/js/213.64691857.js"><link rel="prefetch" href="/assets/js/214.bf089249.js"><link rel="prefetch" href="/assets/js/215.285eccf0.js"><link rel="prefetch" href="/assets/js/216.e135c2e1.js"><link rel="prefetch" href="/assets/js/217.202ec57b.js"><link rel="prefetch" href="/assets/js/218.c1b5175f.js"><link rel="prefetch" href="/assets/js/219.ac0461eb.js"><link rel="prefetch" href="/assets/js/22.67350c09.js"><link rel="prefetch" href="/assets/js/220.944daf1d.js"><link rel="prefetch" href="/assets/js/221.73cba4fb.js"><link rel="prefetch" href="/assets/js/222.cd29efdd.js"><link rel="prefetch" href="/assets/js/223.5c40831a.js"><link rel="prefetch" href="/assets/js/224.0a05890e.js"><link rel="prefetch" href="/assets/js/225.7c20df0a.js"><link rel="prefetch" href="/assets/js/226.c7f69539.js"><link rel="prefetch" href="/assets/js/227.9ccb8852.js"><link rel="prefetch" href="/assets/js/228.77bd4f2e.js"><link rel="prefetch" href="/assets/js/229.1c656481.js"><link rel="prefetch" href="/assets/js/23.308ebe55.js"><link rel="prefetch" href="/assets/js/230.a3adae2f.js"><link rel="prefetch" href="/assets/js/231.f40d7e85.js"><link rel="prefetch" href="/assets/js/232.762cdd7e.js"><link rel="prefetch" href="/assets/js/233.29242686.js"><link rel="prefetch" href="/assets/js/24.2bb08ee4.js"><link rel="prefetch" href="/assets/js/25.3f1e32c9.js"><link rel="prefetch" href="/assets/js/26.6d06f7bc.js"><link rel="prefetch" href="/assets/js/27.edfd52de.js"><link rel="prefetch" href="/assets/js/28.8c959146.js"><link rel="prefetch" href="/assets/js/29.1621b6ed.js"><link rel="prefetch" href="/assets/js/30.751b1f17.js"><link rel="prefetch" href="/assets/js/31.ec665a74.js"><link rel="prefetch" href="/assets/js/32.1b1d72d5.js"><link rel="prefetch" href="/assets/js/33.b5bd40a4.js"><link rel="prefetch" href="/assets/js/34.2155b0a7.js"><link rel="prefetch" href="/assets/js/35.d59b534a.js"><link rel="prefetch" href="/assets/js/36.44a9c35e.js"><link rel="prefetch" href="/assets/js/37.fd11aa80.js"><link rel="prefetch" href="/assets/js/38.a788fd7b.js"><link rel="prefetch" href="/assets/js/39.0099a8f6.js"><link rel="prefetch" href="/assets/js/4.ff489266.js"><link rel="prefetch" href="/assets/js/40.40c37c27.js"><link rel="prefetch" href="/assets/js/41.a1008003.js"><link rel="prefetch" href="/assets/js/42.5b767b44.js"><link rel="prefetch" href="/assets/js/43.3f71078e.js"><link rel="prefetch" href="/assets/js/44.ad24f6c3.js"><link rel="prefetch" href="/assets/js/45.25e40b16.js"><link rel="prefetch" href="/assets/js/46.b64da983.js"><link rel="prefetch" href="/assets/js/47.17f22f05.js"><link rel="prefetch" href="/assets/js/48.98ac4a14.js"><link rel="prefetch" href="/assets/js/49.2d4cfb58.js"><link rel="prefetch" href="/assets/js/5.90f8b8b3.js"><link rel="prefetch" href="/assets/js/50.961c11a7.js"><link rel="prefetch" href="/assets/js/51.c69cd332.js"><link rel="prefetch" href="/assets/js/52.63e40184.js"><link rel="prefetch" href="/assets/js/53.ed393af3.js"><link rel="prefetch" href="/assets/js/54.e8bf699e.js"><link rel="prefetch" href="/assets/js/55.8c4b1f29.js"><link rel="prefetch" href="/assets/js/56.3ea6fad9.js"><link rel="prefetch" href="/assets/js/57.e355b934.js"><link rel="prefetch" href="/assets/js/58.591ee455.js"><link rel="prefetch" href="/assets/js/59.f75a544d.js"><link rel="prefetch" href="/assets/js/6.01e8382f.js"><link rel="prefetch" href="/assets/js/60.941e9812.js"><link rel="prefetch" href="/assets/js/61.4f56972b.js"><link rel="prefetch" href="/assets/js/62.bd0a67cc.js"><link rel="prefetch" href="/assets/js/63.5525dd5a.js"><link rel="prefetch" href="/assets/js/64.2e40b93d.js"><link rel="prefetch" href="/assets/js/65.7d7a1250.js"><link rel="prefetch" href="/assets/js/66.e88829f6.js"><link rel="prefetch" href="/assets/js/67.233ad823.js"><link rel="prefetch" href="/assets/js/68.037f0252.js"><link rel="prefetch" href="/assets/js/69.d5c49911.js"><link rel="prefetch" href="/assets/js/7.67b2aba3.js"><link rel="prefetch" href="/assets/js/70.039de8ef.js"><link rel="prefetch" href="/assets/js/71.fbbf266f.js"><link rel="prefetch" href="/assets/js/72.2cbdccb2.js"><link rel="prefetch" href="/assets/js/73.f824da30.js"><link rel="prefetch" href="/assets/js/74.c8e2bd49.js"><link rel="prefetch" href="/assets/js/75.2b326b41.js"><link rel="prefetch" href="/assets/js/76.6d52ed9a.js"><link rel="prefetch" href="/assets/js/77.6c219b3f.js"><link rel="prefetch" href="/assets/js/78.e3a6099c.js"><link rel="prefetch" href="/assets/js/79.847c1106.js"><link rel="prefetch" href="/assets/js/8.27cd446a.js"><link rel="prefetch" href="/assets/js/80.8fda2f41.js"><link rel="prefetch" href="/assets/js/81.c64976c1.js"><link rel="prefetch" href="/assets/js/82.44648201.js"><link rel="prefetch" href="/assets/js/83.a02ab71e.js"><link rel="prefetch" href="/assets/js/84.da4c143b.js"><link rel="prefetch" href="/assets/js/85.1e5083cb.js"><link rel="prefetch" href="/assets/js/86.373e4575.js"><link rel="prefetch" href="/assets/js/87.b7dae2d7.js"><link rel="prefetch" href="/assets/js/88.40e11959.js"><link rel="prefetch" href="/assets/js/89.711c7107.js"><link rel="prefetch" href="/assets/js/9.a4f4c6a7.js"><link rel="prefetch" href="/assets/js/90.36268f74.js"><link rel="prefetch" href="/assets/js/91.0b92ab60.js"><link rel="prefetch" href="/assets/js/92.9813b046.js"><link rel="prefetch" href="/assets/js/93.fcd128dd.js"><link rel="prefetch" href="/assets/js/94.c099f487.js"><link rel="prefetch" href="/assets/js/95.8042bfa3.js"><link rel="prefetch" href="/assets/js/96.45fc745f.js"><link rel="prefetch" href="/assets/js/97.32326e64.js"><link rel="prefetch" href="/assets/js/98.82035585.js"><link rel="prefetch" href="/assets/js/99.b24b3ac3.js">
    <link rel="stylesheet" href="/assets/css/0.styles.95a28cfa.css">
  </head>
  <body class="theme-mode-light">
    <div id="app" data-server-rendered="true"><div class="theme-container sidebar-open have-rightmenu have-body-img"><header class="navbar blur"><div title="目录" class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/img/EB-logo.png" alt="Keith's blog" class="logo"> <span class="site-name can-hide">Keith's blog</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><a href="/pages/294b0a/" class="nav-link">个人总结</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="来自大佬的文章" class="dropdown-title"><!----> <span class="title" style="display:;">来自大佬的文章</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><h4>前端</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/8143cc480faf9a11/" class="nav-link">前端文章</a></li><li class="dropdown-subitem"><a href="/note/javascript/" class="nav-link">学习笔记</a></li></ul></li><li class="dropdown-item"><h4>页面</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/8309a5b876fc95e3/" class="nav-link">HTML</a></li><li class="dropdown-subitem"><a href="/pages/0a83b083bdf257cb/" class="nav-link">CSS</a></li></ul></li><li class="dropdown-item"><h4>技术</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/9a7ee40fc232253e/" class="nav-link">技术文档</a></li><li class="dropdown-subitem"><a href="/pages/4c778760be26d8b3/" class="nav-link">GitHub技巧</a></li><li class="dropdown-subitem"><a href="/pages/117708e0af7f0bd9/" class="nav-link">Nodejs</a></li><li class="dropdown-subitem"><a href="/pages/41f87d890d0a02af/" class="nav-link">博客搭建</a></li></ul></li><li class="dropdown-item"><h4>更多</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/f2a556/" class="nav-link">学习</a></li><li class="dropdown-subitem"><a href="/pages/aea6571b7a8bae86/" class="nav-link">面试</a></li><li class="dropdown-subitem"><a href="/pages/2d615df9a36a98ed/" class="nav-link">心情杂货</a></li><li class="dropdown-subitem"><a href="/pages/baaa02/" class="nav-link">实用技巧</a></li></ul></li><li class="dropdown-item"><!----> <a href="/about/" class="nav-link">关于</a></li><li class="dropdown-item"><!----> <a href="/pages/beb6c0bd8a66cea6/" class="nav-link">收藏</a></li><li class="dropdown-item"><h4>索引</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/categories/" class="nav-link">分类</a></li><li class="dropdown-subitem"><a href="/tags/" class="nav-link">标签</a></li><li class="dropdown-subitem"><a href="/archives/" class="nav-link">归档</a></li></ul></li></ul></div></div> <a href="https://github.com/yangkeith/vuepress-theme-vdoing" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar" style="display:none;"><div class="blogger"><img src="/img/avatar.jpg"> <div class="blogger-info"><h3>Keith</h3> <span>无名小卒</span></div></div> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><a href="/pages/294b0a/" class="nav-link">个人总结</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="来自大佬的文章" class="dropdown-title"><!----> <span class="title" style="display:;">来自大佬的文章</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><h4>前端</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/8143cc480faf9a11/" class="nav-link">前端文章</a></li><li class="dropdown-subitem"><a href="/note/javascript/" class="nav-link">学习笔记</a></li></ul></li><li class="dropdown-item"><h4>页面</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/8309a5b876fc95e3/" class="nav-link">HTML</a></li><li class="dropdown-subitem"><a href="/pages/0a83b083bdf257cb/" class="nav-link">CSS</a></li></ul></li><li class="dropdown-item"><h4>技术</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/9a7ee40fc232253e/" class="nav-link">技术文档</a></li><li class="dropdown-subitem"><a href="/pages/4c778760be26d8b3/" class="nav-link">GitHub技巧</a></li><li class="dropdown-subitem"><a href="/pages/117708e0af7f0bd9/" class="nav-link">Nodejs</a></li><li class="dropdown-subitem"><a href="/pages/41f87d890d0a02af/" class="nav-link">博客搭建</a></li></ul></li><li class="dropdown-item"><h4>更多</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/pages/f2a556/" class="nav-link">学习</a></li><li class="dropdown-subitem"><a href="/pages/aea6571b7a8bae86/" class="nav-link">面试</a></li><li class="dropdown-subitem"><a href="/pages/2d615df9a36a98ed/" class="nav-link">心情杂货</a></li><li class="dropdown-subitem"><a href="/pages/baaa02/" class="nav-link">实用技巧</a></li></ul></li><li class="dropdown-item"><!----> <a href="/about/" class="nav-link">关于</a></li><li class="dropdown-item"><!----> <a href="/pages/beb6c0bd8a66cea6/" class="nav-link">收藏</a></li><li class="dropdown-item"><h4>索引</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/categories/" class="nav-link">分类</a></li><li class="dropdown-subitem"><a href="/tags/" class="nav-link">标签</a></li><li class="dropdown-subitem"><a href="/archives/" class="nav-link">归档</a></li></ul></li></ul></div></div> <a href="https://github.com/yangkeith/vuepress-theme-vdoing" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><a href="/pages/f344d070a1031ef7/" class="sidebar-link">ECMAScript 6 简介</a></li><li><a href="/pages/c1edd70a6b7c7872/" class="sidebar-link">let 和 const 命令</a></li><li><a href="/pages/b1ab10a62f7564da/" class="sidebar-link">变量的解构赋值</a></li><li><a href="/pages/ca89eca8adeba5f4/" class="sidebar-link">字符串的扩展</a></li><li><a href="/pages/a650b4a0ebfc9350/" class="sidebar-link">字符串的新增方法</a></li><li><a href="/pages/0473261a6ab0ee8c/" class="sidebar-link">正则的扩展</a></li><li><a href="/pages/5dfea9a0f2d1a392/" class="sidebar-link">数值的扩展</a></li><li><a href="/pages/8ed309d668b20264/" class="sidebar-link">函数的扩展</a></li><li><a href="/pages/e34009d60d8bc4b2/" class="sidebar-link">数组的扩展</a></li><li><a href="/pages/b5e3e0a0ff6e9c25/" class="sidebar-link">对象的扩展</a></li><li><a href="/pages/e85e68947502cf90/" class="sidebar-link">对象的新增方法</a></li><li><a href="/pages/02c86eb2792f3262/" class="sidebar-link">Symbol</a></li><li><a href="/pages/0c21dae358fca16b/" class="sidebar-link">Set 和 Map 数据结构</a></li><li><a href="/pages/f56ec2ab97d60483/" class="sidebar-link">Proxy</a></li><li><a href="/pages/74de3e45e4491e95/" class="sidebar-link">Reflect</a></li><li><a href="/pages/2810ae8985e9bd52/" class="sidebar-link">Promise 对象</a></li><li><a href="/pages/48df907ad3570f3d/" class="sidebar-link">Iterator 和 for-of 循环</a></li><li><a href="/pages/718b48ed9ce0adce/" class="sidebar-link">Generator 函数的语法</a></li><li><a href="/pages/75af7031eb66847b/" class="sidebar-link">Generator 函数的异步应用</a></li><li><a href="/pages/3777253e65bac487/" class="sidebar-link">async 函数</a></li><li><a href="/pages/e831e1593c82bbe0/" class="sidebar-link">Class 的基本语法</a></li><li><a href="/pages/83f8c3a0cd87dd83/" class="sidebar-link">Class 的继承</a></li><li><a href="/pages/efe2fb04eb8ac5fb/" class="sidebar-link">Module 的语法</a></li><li><a href="/pages/a79ca2e64ceae213/" aria-current="page" class="active sidebar-link">Module 的加载实现</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#浏览器加载" class="sidebar-link">浏览器加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#传统方法" class="sidebar-link">传统方法</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#加载规则" class="sidebar-link">加载规则</a></li></ul></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#es6-模块与-commonjs-模块的差异" class="sidebar-link">ES6 模块与 CommonJS 模块的差异</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#node-js-加载" class="sidebar-link">Node.js 加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#概述" class="sidebar-link">概述</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#main-字段" class="sidebar-link">main 字段</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#exports-字段" class="sidebar-link">exports 字段</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#es6-模块加载-commonjs-模块" class="sidebar-link">ES6 模块加载 CommonJS 模块</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#commonjs-模块加载-es6-模块" class="sidebar-link">CommonJS 模块加载 ES6 模块</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#node-js-的内置模块" class="sidebar-link">Node.js 的内置模块</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#加载路径" class="sidebar-link">加载路径</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#内部变量" class="sidebar-link">内部变量</a></li></ul></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#循环加载" class="sidebar-link">循环加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#commonjs-模块的加载原理" class="sidebar-link">CommonJS 模块的加载原理</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#commonjs-模块的循环加载" class="sidebar-link">CommonJS 模块的循环加载</a></li><li class="sidebar-sub-header"><a href="/pages/a79ca2e64ceae213/#es6-模块的循环加载" class="sidebar-link">ES6 模块的循环加载</a></li></ul></li></ul></li><li><a href="/pages/984bf549204bb266/" class="sidebar-link">编程风格</a></li><li><a href="/pages/32c35f7651d6e58e/" class="sidebar-link">读懂 ECMAScript 规格</a></li><li><a href="/pages/16121351be68691b/" class="sidebar-link">异步遍历器</a></li><li><a href="/pages/a2ba314746bfdbdd/" class="sidebar-link">ArrayBuffer</a></li><li><a href="/pages/7188882b8d65af1b/" class="sidebar-link">最新提案</a></li><li><a href="/pages/e97bc1e5626b082c/" class="sidebar-link">装饰器</a></li><li><a href="/pages/1cf50330655efc69/" class="sidebar-link">函数式编程</a></li><li><a href="/pages/6a8e2dc558da1b39/" class="sidebar-link">Mixin</a></li><li><a href="/pages/8e8f80f69b775a56/" class="sidebar-link">SIMD</a></li><li><a href="/pages/ea6f3b870f6dab69/" class="sidebar-link">参考链接</a></li></ul> <div class="sidebar-slot sidebar-slot-bottom"><!-- 正方形 -->
      <ins class="adsbygoogle"
          style="display:block"
          data-ad-client="ca-pub-7828333725993554"
          data-ad-slot="3508773082"
          data-ad-format="auto"
          data-full-width-responsive="true"></ins>
      <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
      </script></div></aside> <div><main class="page"><div class="theme-vdoing-wrapper "><div class="articleInfo-wrap" data-v-70a2d273><div class="articleInfo" data-v-70a2d273><ul class="breadcrumbs" data-v-70a2d273><li data-v-70a2d273><a href="/" title="首页" class="iconfont icon-home router-link-active" data-v-70a2d273></a></li> <li data-v-70a2d273><a href="/note/es6/" title="《ES6 教程》笔记-目录页" data-v-70a2d273>《ES6 教程》笔记</a></li> <!----> <!----></ul> <div class="info" data-v-70a2d273><div title="作者" class="author iconfont icon-touxiang" data-v-70a2d273><a href="javascript:;" data-v-70a2d273>阮一峰</a></div> <div title="创建时间" class="date iconfont icon-riqi" data-v-70a2d273><a href="javascript:;" data-v-70a2d273>2020-02-09</a></div> <!----></div></div></div> <!----> <div class="content-wrapper"><div class="right-menu-wrapper"><div class="right-menu-margin"><div class="right-menu-content"></div></div></div> <h1><img src="">
          Module 的加载实现
        </h1> <div class="page-slot page-slot-top"><!-- 固定100% * 90px可显示，max-height:90px未见显示-->
     <ins class="adsbygoogle"
          style="display:inline-block;width:100%;max-height:90px"
          data-ad-client="ca-pub-7828333725993554"
          data-ad-slot="6625304284"></ins>
      <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
      </script></div> <div class="theme-vdoing-content content__default"><h1 id="module-的加载实现"><a href="#module-的加载实现" class="header-anchor">#</a> Module 的加载实现</h1> <p>上一章介绍了模块的语法，本章介绍如何在浏览器和 Node.js 之中加载 ES6 模块，以及实际开发中经常遇到的一些问题（比如循环加载）。
</p> <h2 id="浏览器加载"><a href="#浏览器加载" class="header-anchor">#</a> 浏览器加载</h2> <h3 id="传统方法"><a href="#传统方法" class="header-anchor">#</a> 传统方法</h3> <p>HTML 网页中，浏览器通过<code>&lt;script&gt;</code>标签加载 JavaScript 脚本。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token comment">&lt;!-- 页面内嵌的脚本 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>application/javascript<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token comment">// module code</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token comment">&lt;!-- 外部脚本 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>application/javascript<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中，由于浏览器脚本的默认语言是 JavaScript，因此<code>type=&quot;application/javascript&quot;</code>可以省略。</p> <p>默认情况下，浏览器是同步加载 JavaScript 脚本，即渲染引擎遇到<code>&lt;script&gt;</code>标签就会停下来，等到执行完脚本，再继续向下渲染。如果是外部脚本，还必须加入脚本下载的时间。</p> <p>如果脚本体积很大，下载和执行的时间就会很长，因此造成浏览器堵塞，用户会感觉到浏览器“卡死”了，没有任何响应。这显然是很不好的体验，所以浏览器允许脚本异步加载，下面就是两种异步加载的语法。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">defer</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">async</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>上面代码中，<code>&lt;script&gt;</code>标签打开<code>defer</code>或<code>async</code>属性，脚本就会异步加载。渲染引擎遇到这一行命令，就会开始下载外部脚本，但不会等它下载和执行，而是直接执行后面的命令。</p> <p><code>defer</code>与<code>async</code>的区别是：<code>defer</code>要等到整个页面在内存中正常渲染结束（DOM 结构完全生成，以及其他脚本执行完成），才会执行；<code>async</code>一旦下载完，渲染引擎就会中断渲染，执行这个脚本以后，再继续渲染。一句话，<strong><code>defer</code>是“渲染完再执行”，<code>async</code>是“下载完就执行”</strong>。另外，如果有多个<code>defer</code>脚本，会按照它们在页面出现的顺序加载，而多个<code>async</code>脚本是不能保证加载顺序的。</p> <h3 id="加载规则"><a href="#加载规则" class="header-anchor">#</a> 加载规则</h3> <p>浏览器加载 ES6 模块，也使用<code>&lt;script&gt;</code>标签，但是要加入<code>type=&quot;module&quot;</code>属性。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>上面代码在网页中插入一个模块<code>foo.js</code>，由于<code>type</code>属性设为<code>module</code>，所以浏览器知道这是一个 ES6 模块。</p> <p>浏览器对于<strong>带有<code>type=&quot;module&quot;</code>的<code>&lt;script&gt;</code>，都是异步加载</strong>，不会造成堵塞浏览器，即等到整个页面渲染完，再执行模块脚本，<strong>等同于打开了<code>&lt;script&gt;</code>标签的<code>defer</code>属性。</strong></p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 等同于 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">defer</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>如果网页有多个<code>&lt;script type=&quot;module&quot;&gt;</code>，它们会按照在页面出现的顺序依次执行。</p> <p><code>&lt;script&gt;</code>标签的<code>async</code>属性也可以打开，这时只要加载完成，渲染引擎就会中断渲染立即执行。执行完成后，再恢复渲染。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">async</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>一旦使用了<code>async</code>属性，<code>&lt;script type=&quot;module&quot;&gt;</code>就不会按照在页面出现的顺序执行，而是只要该模块加载完成，就执行该模块。</p> <p>ES6 模块也允许内嵌在网页中，语法行为与加载外部脚本完全一致。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> utils <span class="token keyword">from</span> <span class="token string">&quot;./utils.js&quot;</span><span class="token punctuation">;</span>

  <span class="token comment">// other code</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>举例来说，jQuery 就支持模块加载。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> $ <span class="token keyword">from</span> <span class="token string">&quot;./jquery/src/jquery.js&quot;</span><span class="token punctuation">;</span>
  <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#message'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token string">'Hi from jQuery!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>对于外部的模块脚本（上例是<code>foo.js</code>），有几点需要注意。</p> <ul><li>代码是<strong>在模块作用域之中运行</strong>，而不是在全局作用域运行。模块内部的顶层变量，外部不可见。</li> <li>模块脚本<strong>自动采用严格模式</strong>，不管有没有声明<code>use strict</code>。</li> <li>模块之中，可以使用<code>import</code>命令加载其他模块（<code>.js</code>后缀不可省略，需要提供绝对 URL 或相对 URL），也可以使用<code>export</code>命令输出对外接口。</li> <li>模块之中，<strong>顶层的<code>this</code>关键字返回<code>undefined</code></strong>，而不是指向<code>window</code>。也就是说，在模块顶层使用<code>this</code>关键字，是无意义的。</li> <li>同一个模块如果加载多次，将只执行一次。</li></ul> <p>下面是一个示例模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> utils <span class="token keyword">from</span> <span class="token string">'https://example.com/js/utils.js'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> x <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x <span class="token operator">===</span> window<span class="token punctuation">.</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//false</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// true</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>利用顶层的<code>this</code>等于<code>undefined</code>这个语法点，可以侦测当前代码是否在 ES6 模块之中。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">const</span> isNotModuleScript <span class="token operator">=</span> <span class="token keyword">this</span> <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h2 id="es6-模块与-commonjs-模块的差异"><a href="#es6-模块与-commonjs-模块的差异" class="header-anchor">#</a> ES6 模块与 CommonJS 模块的差异</h2> <p>讨论 Node.js 加载 ES6 模块之前，必须了解 ES6 模块与 CommonJS 模块完全不同。</p> <p>它们有<strong>两个重大差异</strong>。</p> <ul><li><strong>CommonJS 模块输出的是一个值的拷贝，ES6 模块输出的是值的引用。</strong></li> <li><strong>CommonJS 模块是运行时加载，ES6 模块是编译时输出接口。</strong></li></ul> <p>第二个差异是因为 CommonJS 加载的是一个对象（即<code>module.exports</code>属性），该对象只有在脚本运行完才会生成。而 ES6 模块不是对象，它的对外接口只是一种静态定义，在代码静态解析阶段就会生成。</p> <p>下面重点解释第一个差异。</p> <p>CommonJS 模块输出的是值的拷贝，也就是说，一旦输出一个值，模块内部的变化就影响不到这个值。请看下面这个模块文件<code>lib.js</code>的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
<span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  counter<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  counter<span class="token operator">:</span> counter<span class="token punctuation">,</span>
  incCounter<span class="token operator">:</span> incCounter<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码输出内部变量<code>counter</code>和改写这个变量的内部方法<code>incCounter</code>。然后，在<code>main.js</code>里面加载这个模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// main.js</span>
<span class="token keyword">var</span> mod <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./lib'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>mod<span class="token punctuation">.</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 3</span>
mod<span class="token punctuation">.</span><span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>mod<span class="token punctuation">.</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码说明，<code>lib.js</code>模块加载以后，它的内部变化就影响不到输出的<code>mod.counter</code>了。这是因为<code>mod.counter</code>是一个原始类型的值，会被缓存。除非写成一个函数，才能得到内部变动后的值。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
<span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  counter<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token keyword">get</span> <span class="token function">counter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> counter
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  incCounter<span class="token operator">:</span> incCounter<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码中，输出的<code>counter</code>属性实际上是一个取值器函数。现在再执行<code>main.js</code>，就可以正确读取内部变量<code>counter</code>的变动了。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ node main.js
<span class="token number">3</span>
<span class="token number">4</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候，遇到模块加载命令<code>import</code>，就会生成一个只读引用。等到脚本真正执行时，再根据这个只读引用，到被加载的那个模块里面去取值。换句话说，ES6 的<code>import</code>有点像 Unix 系统的“符号连接”，原始值变了，<code>import</code>加载的值也会跟着变。因此，<strong>ES6 模块是动态引用，并且不会缓存值</strong>，模块里面的变量绑定其所在的模块。</p> <p>还是举上面的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
<span class="token keyword">export</span> <span class="token keyword">let</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  counter<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// main.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> counter<span class="token punctuation">,</span> incCounter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./lib'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
<span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 4</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码说明，ES6 模块输入的变量<code>counter</code>是活的，完全反应其所在模块<code>lib.js</code>内部的变化。</p> <p>再举一个出现在<code>export</code>一节中的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// m1.js</span>
<span class="token keyword">export</span> <span class="token keyword">var</span> foo <span class="token operator">=</span> <span class="token string">'bar'</span><span class="token punctuation">;</span>
<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> foo <span class="token operator">=</span> <span class="token string">'baz'</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// m2.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./m1.js'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中，<code>m1.js</code>的变量<code>foo</code>，在刚加载时等于<code>bar</code>，过了 500 毫秒，又变为等于<code>baz</code>。</p> <p>让我们看看，<code>m2.js</code>能否正确读取这个变化。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ babel-node m2.js

bar
baz
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>上面代码表明，ES6 模块不会缓存运行结果，而是动态地去被加载的模块取值，并且变量总是绑定其所在的模块。</p> <p>由于 ES6 输入的模块变量，只是一个“符号连接”，所以<strong>这个变量是只读的，对它进行重新赋值会报错</strong>。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
<span class="token keyword">export</span> <span class="token keyword">let</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// main.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> obj <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./lib'</span><span class="token punctuation">;</span>

obj<span class="token punctuation">.</span>prop <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span> <span class="token comment">// OK</span>
obj <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// TypeError</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中，<code>main.js</code>从<code>lib.js</code>输入变量<code>obj</code>，可以对<code>obj</code>添加属性，但是重新赋值就会报错。因为变量<code>obj</code>指向的地址是只读的，不能重新赋值，这就好比<code>main.js</code>创造了一个名为<code>obj</code>的<code>const</code>变量。</p> <p>最后，<code>export</code>通过接口，输出的是同一个值。不同的脚本加载这个接口，得到的都是同样的实例。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// mod.js</span>
<span class="token keyword">function</span> <span class="token constant">C</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>sum <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">show</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sum<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">let</span> c <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">C</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>上面的脚本<code>mod.js</code>，输出的是一个<code>C</code>的实例。不同的脚本加载这个模块，得到的都是同一个实例。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// x.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>c<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./mod'</span><span class="token punctuation">;</span>
c<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// y.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>c<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./mod'</span><span class="token punctuation">;</span>
c<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// main.js</span>
<span class="token keyword">import</span> <span class="token string">'./x'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token string">'./y'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>现在执行<code>main.js</code>，输出的是<code>1</code>。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ babel-node main.js
<span class="token number">1</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>这就证明了<code>x.js</code>和<code>y.js</code>加载的都是<code>C</code>的同一个实例。</p> <h2 id="node-js-加载"><a href="#node-js-加载" class="header-anchor">#</a> Node.js 加载</h2> <h3 id="概述"><a href="#概述" class="header-anchor">#</a> 概述</h3> <p>Node.js 对 ES6 模块的处理比较麻烦，因为它有自己的 CommonJS 模块格式，与 ES6 模块格式是不兼容的。目前的解决方案是，将两者分开，ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始，Node.js 已经默认打开了 ES6 模块支持。</p> <p>Node.js 要求 ES6 模块<strong>采用<code>.mjs</code>后缀文件名</strong>。也就是说，只要脚本文件里面使用<code>import</code>或者<code>export</code>命令，那么就必须采用<code>.mjs</code>后缀名。Node.js 遇到<code>.mjs</code>文件，就认为它是 ES6 模块，默认启用严格模式，不必在每个模块文件顶部指定<code>&quot;use strict&quot;</code>。</p> <p><strong>如果不希望将后缀名改成<code>.mjs</code>，可以在项目的<code>package.json</code>文件中，指定<code>type</code>字段为<code>module</code>。</strong></p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
   <span class="token string">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>一旦设置了以后，该目录里面的 JS 脚本，就被解释用 ES6 模块。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code><span class="token comment"># 解释成 ES6 模块</span>
$ node my-app.js
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>如果这时还要使用 CommonJS 模块，那么<strong>需要将 CommonJS 脚本的后缀名都改成<code>.cjs</code>。如果没有<code>type</code>字段，或者<code>type</code>字段为<code>commonjs</code>，则<code>.js</code>脚本会被解释成 CommonJS 模块</strong>。</p> <p>总结为一句话：<strong><code>.mjs</code>文件总是以 ES6 模块加载，<code>.cjs</code>文件总是以 CommonJS 模块加载，<code>.js</code>文件的加载取决于<code>package.json</code>里面<code>type</code>字段的设置</strong>。</p> <p>注意，ES6 模块与 CommonJS 模块尽量<strong>不要混用</strong>。<code>require</code>命令不能加载<code>.mjs</code>文件，会报错，只有<code>import</code>命令才可以加载<code>.mjs</code>文件。反过来，<code>.mjs</code>文件里面也不能使用<code>require</code>命令，必须使用<code>import</code>。</p> <h3 id="main-字段"><a href="#main-字段" class="header-anchor">#</a> main 字段</h3> <p><code>package.json</code>文件有两个字段可以指定模块的入口文件：<code>main</code>和<code>exports</code>。比较简单的模块，可以只使用<code>main</code>字段，指定模块加载的入口文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
<span class="token punctuation">{</span>
  <span class="token string">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/index.js&quot;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码指定项目的入口脚本为<code>./src/index.js</code>，它的格式为 ES6 模块。如果没有<code>type</code>字段，<code>index.js</code>就会被解释为 CommonJS 模块。</p> <p>然后，<code>import</code>命令就可以加载这个模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./my-app.mjs</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> something <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'es-module-package'</span><span class="token punctuation">;</span>
<span class="token comment">// 实际加载的是 ./node_modules/es-module-package/src/index.js</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>上面代码中，<strong>运行该脚本以后，Node.js 就会到<code>./node_modules</code>目录下面，寻找<code>es-module-package</code>模块，然后根据该模块<code>package.json</code>的<code>main</code>字段去执行入口文件。</strong></p> <p>这时，如果用 CommonJS 模块的<code>require()</code>命令去加载<code>es-module-package</code>模块会报错，因为 CommonJS 模块不能处理<code>export</code>命令。</p> <h3 id="exports-字段"><a href="#exports-字段" class="header-anchor">#</a> exports 字段</h3> <p><strong><code>exports</code>字段的优先级高于<code>main</code>字段</strong>。它有多种用法。</p> <p>（1）<strong>子目录别名</strong></p> <p><code>package.json</code>文件的<code>exports</code>字段可以指定脚本或子目录的别名。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
<span class="token punctuation">{</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;./submodule&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/submodule.js&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面的代码指定<code>src/submodule.js</code>别名为<code>submodule</code>，然后就可以从别名加载这个文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'es-module-package/submodule'</span><span class="token punctuation">;</span>
<span class="token comment">// 加载 ./node_modules/es-module-package/src/submodule.js</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>下面是子目录别名的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
<span class="token punctuation">{</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;./features/&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/features/&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">import</span> feature <span class="token keyword">from</span> <span class="token string">'es-module-package/features/x.js'</span><span class="token punctuation">;</span>
<span class="token comment">// 加载 ./node_modules/es-module-package/src/features/x.js</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>如果没有指定别名，就不能用“模块+脚本名”这种形式加载脚本。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 报错</span>
<span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'es-module-package/private-module.js'</span><span class="token punctuation">;</span>

<span class="token comment">// 不报错</span>
<span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'./node_modules/es-module-package/private-module.js'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>（2）main 的别名</p> <p><code>exports</code>字段的别名如果是<code>.</code>，就代表模块的主入口，优先级高于<code>main</code>字段，并且可以直接简写成<code>exports</code>字段的值。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;.&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 等同于</span>
<span class="token punctuation">{</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p><strong>由于<code>exports</code>字段只有支持 ES6 的 Node.js 才认识，所以可以用来兼容旧版本的 Node.js</strong>。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  <span class="token string">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main-legacy.cjs&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;.&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main-modern.cjs&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码中，老版本的 Node.js （不支持 ES6 模块）的入口文件是<code>main-legacy.cjs</code>，新版本的 Node.js 的入口文件是<code>main-modern.cjs</code>。</p> <p><strong>（3）条件加载</strong></p> <p>利用<code>.</code>这个别名，可以为 ES6 模块和 CommonJS 指定不同的入口。目前，这个功能需要在 Node.js 运行的时候，打开<code>--experimental-conditional-exports</code>标志。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  <span class="token string">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;.&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
      <span class="token string">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码中，别名<code>.</code>的<code>require</code>条件指定<code>require()</code>命令的入口文件（即 CommonJS 的入口），<code>default</code>条件指定其他情况的入口（即 ES6 的入口）。</p> <p>上面的写法可以简写如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>注意，如果同时还有其他别名，就不能采用简写，否则或报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  <span class="token comment">// 报错</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;./feature&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./lib/feature.js&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h3 id="es6-模块加载-commonjs-模块"><a href="#es6-模块加载-commonjs-模块" class="header-anchor">#</a> ES6 模块加载 CommonJS 模块</h3> <p>目前，一个模块同时支持 ES6 和 CommonJS 两种格式的常见方法是，<code>package.json</code>文件的<code>main</code>字段指定 CommonJS 入口，给 Node.js 使用；<code>module</code>字段指定 ES6 模块入口，给打包工具使用，因为 Node.js 不认识<code>module</code>字段。</p> <p>有了上一节的条件加载以后，Node.js 本身就可以同时处理两种模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/package.json</span>
<span class="token punctuation">{</span>
  <span class="token string">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.cjs&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.cjs&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./wrapper.mjs&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码指定了 CommonJS 入口文件<code>index.cjs</code>，下面是这个文件的代码。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/index.cjs</span>
exports<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'value'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>然后，ES6 模块可以加载这个文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/wrapper.mjs</span>
<span class="token keyword">import</span> cjsModule <span class="token keyword">from</span> <span class="token string">'./index.cjs'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> name <span class="token operator">=</span> cjsModule<span class="token punctuation">.</span>name<span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>注意，<code>import</code>命令加载 CommonJS 模块，只能整体加载，不能只加载单一的输出项。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 正确</span>
<span class="token keyword">import</span> packageMain <span class="token keyword">from</span> <span class="token string">'commonjs-package'</span><span class="token punctuation">;</span>

<span class="token comment">// 报错</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> method <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'commonjs-package'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>还有一种变通的加载方法，就是使用 Node.js 内置的<code>module.createRequire()</code>方法。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// cjs.cjs</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token string">'cjs'</span><span class="token punctuation">;</span>

<span class="token comment">// esm.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> createRequire <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'module'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> require <span class="token operator">=</span> <span class="token function">createRequire</span><span class="token punctuation">(</span><span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> cjs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./cjs.cjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
cjs <span class="token operator">===</span> <span class="token string">'cjs'</span><span class="token punctuation">;</span> <span class="token comment">// true</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中，ES6 模块通过<code>module.createRequire()</code>方法可以加载 CommonJS 模块</p> <h3 id="commonjs-模块加载-es6-模块"><a href="#commonjs-模块加载-es6-模块" class="header-anchor">#</a> CommonJS 模块加载 ES6 模块</h3> <p>CommonJS 的<code>require</code>命令不能加载 ES6 模块，会报错，只能使用<code>import()</code>这个方法加载。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token string">'./my-app.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>上面代码可以在 CommonJS 模块中运行。</p> <h3 id="node-js-的内置模块"><a href="#node-js-的内置模块" class="header-anchor">#</a> Node.js 的内置模块</h3> <p>Node.js 的内置模块可以整体加载，也可以加载指定的输出项。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 整体加载</span>
<span class="token keyword">import</span> EventEmitter <span class="token keyword">from</span> <span class="token string">'events'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> e <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 加载指定的输出项</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> readFile <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'fs'</span><span class="token punctuation">;</span>
<span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">'./foo.txt'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> source</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h3 id="加载路径"><a href="#加载路径" class="header-anchor">#</a> 加载路径</h3> <p>ES6 模块的加载路径必须给出脚本的完整路径，不能省略脚本的后缀名。<code>import</code>命令和<code>package.json</code>文件的<code>main</code>字段如果省略脚本的后缀名，会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ES6 模块中将报错</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> something <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./index'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>为了与浏览器的<code>import</code>加载规则相同，Node.js 的<code>.mjs</code>文件支持 URL 路径。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> <span class="token string">'./foo.mjs?query=1'</span><span class="token punctuation">;</span> <span class="token comment">// 加载 ./foo 传入参数 ?query=1</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>上面代码中，脚本路径带有参数<code>?query=1</code>，Node 会按 URL 规则解读。同一个脚本只要参数不同，就会被加载多次，并且保存成不同的缓存。由于这个原因，只要文件名中含有<code>:</code>、<code>%</code>、<code>#</code>、<code>?</code>等特殊字符，最好对这些字符进行转义。</p> <p>目前，Node.js 的<code>import</code>命令只支持加载本地模块（<code>file:</code>协议）和<code>data:</code>协议，不支持加载远程模块。另外，脚本路径只支持相对路径，不支持绝对路径（即以<code>/</code>或<code>//</code>开头的路径）。</p> <p>最后，Node 的<code>import</code>命令是异步加载，这一点与浏览器的处理方法相同。</p> <h3 id="内部变量"><a href="#内部变量" class="header-anchor">#</a> 内部变量</h3> <p>ES6 模块应该是通用的，同一个模块不用修改，就可以用在浏览器环境和服务器环境。为了达到这个目标，Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。</p> <p>首先，就是<code>this</code>关键字。ES6 模块之中，顶层的<code>this</code>指向<code>undefined</code>；CommonJS 模块的顶层<code>this</code>指向当前模块，这是两者的一个重大差异。</p> <p>其次，以下这些顶层变量在 ES6 模块之中都是不存在的。</p> <ul><li><code>arguments</code></li> <li><code>require</code></li> <li><code>module</code></li> <li><code>exports</code></li> <li><code>__filename</code></li> <li><code>__dirname</code></li></ul> <h2 id="循环加载"><a href="#循环加载" class="header-anchor">#</a> 循环加载</h2> <p>“循环加载”（circular dependency）指的是，<code>a</code>脚本的执行依赖<code>b</code>脚本，而<code>b</code>脚本的执行又依赖<code>a</code>脚本。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.js</span>
<span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'b'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// b.js</span>
<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>通常，“循环加载”表示存在强耦合，如果处理不好，还可能导致递归加载，使得程序无法执行，因此应该避免出现。</p> <p>但是实际上，这是很难避免的，尤其是依赖关系复杂的大项目，很容易出现<code>a</code>依赖<code>b</code>，<code>b</code>依赖<code>c</code>，<code>c</code>又依赖<code>a</code>这样的情况。这意味着，模块加载机制必须考虑“循环加载”的情况。</p> <p>对于 JavaScript 语言来说，目前最常见的两种模块格式 CommonJS 和 ES6，处理“循环加载”的方法是不一样的，返回的结果也不一样。</p> <h3 id="commonjs-模块的加载原理"><a href="#commonjs-模块的加载原理" class="header-anchor">#</a> CommonJS 模块的加载原理</h3> <p>介绍 ES6 如何处理“循环加载”之前，先介绍目前最流行的 CommonJS 模块格式的加载原理。</p> <p>CommonJS 的一个模块，就是一个脚本文件。<code>require</code>命令第一次加载该脚本，就会执行整个脚本，然后在内存生成一个对象。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token string">'...'</span><span class="token punctuation">,</span>
  exports<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  loaded<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token operator">...</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码就是 Node 内部加载模块后生成的一个对象。该对象的<code>id</code>属性是模块名，<code>exports</code>属性是模块输出的各个接口，<code>loaded</code>属性是一个布尔值，表示该模块的脚本是否执行完毕。其他还有很多属性，这里都省略了。</p> <p>以后需要用到这个模块的时候，就会到<code>exports</code>属性上面取值。即使再次执行<code>require</code>命令，也不会再次执行该模块，而是到缓存之中取值。也就是说，CommonJS 模块无论加载多少次，都只会在第一次加载时运行一次，以后再加载，就返回第一次运行的结果，除非手动清除系统缓存。</p> <h3 id="commonjs-模块的循环加载"><a href="#commonjs-模块的循环加载" class="header-anchor">#</a> CommonJS 模块的循环加载</h3> <p>CommonJS 模块的重要特性是加载时执行，即脚本代码在<code>require</code>的时候，就会全部执行。一旦出现某个模块被&quot;循环加载&quot;，就只输出已经执行的部分，还未执行的部分不会输出。</p> <p>让我们来看，Node <a href="https://nodejs.org/api/modules.html#modules_cycles" target="_blank" rel="noopener noreferrer">官方文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>里面的例子。脚本文件<code>a.js</code>代码如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./b.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 a.js 之中，b.done = %j'</span><span class="token punctuation">,</span> b<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.js 执行完毕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码之中，<code>a.js</code>脚本先输出一个<code>done</code>变量，然后加载另一个脚本文件<code>b.js</code>。注意，此时<code>a.js</code>代码就停在这里，等待<code>b.js</code>执行完毕，再往下执行。</p> <p>再看<code>b.js</code>的代码。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./a.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 b.js 之中，a.done = %j'</span><span class="token punctuation">,</span> a<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.js 执行完毕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码之中，<code>b.js</code>执行到第二行，就会去加载<code>a.js</code>，这时，就发生了“循环加载”。系统会去<code>a.js</code>模块对应对象的<code>exports</code>属性取值，可是因为<code>a.js</code>还没有执行完，从<code>exports</code>属性只能取回已经执行的部分，而不是最后的值。</p> <p><code>a.js</code>已经执行的部分，只有一行。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>因此，对于<code>b.js</code>来说，它从<code>a.js</code>只输入一个变量<code>done</code>，值为<code>false</code>。</p> <p>然后，<code>b.js</code>接着往下执行，等到全部执行完毕，再把执行权交还给<code>a.js</code>。于是，<code>a.js</code>接着往下执行，直到执行完毕。我们写一个脚本<code>main.js</code>，验证这个过程。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./a.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./b.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 main.js 之中, a.done=%j, b.done=%j'</span><span class="token punctuation">,</span> a<span class="token punctuation">.</span>done<span class="token punctuation">,</span> b<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>执行<code>main.js</code>，运行结果如下。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ node main.js

在 b.js 之中，a.done <span class="token operator">=</span> <span class="token boolean">false</span>
b.js 执行完毕
在 a.js 之中，b.done <span class="token operator">=</span> <span class="token boolean">true</span>
a.js 执行完毕
在 main.js 之中, a.done<span class="token operator">=</span>true, b.done<span class="token operator">=</span>true
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>上面的代码证明了两件事。一是，在<code>b.js</code>之中，<code>a.js</code>没有执行完毕，只执行了第一行。二是，<code>main.js</code>执行到第二行时，不会再次执行<code>b.js</code>，而是输出缓存的<code>b.js</code>的执行结果，即它的第四行。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>总之，CommonJS 输入的是被输出值的拷贝，不是引用。</p> <p>另外，由于 CommonJS 模块遇到循环加载时，返回的是当前已经执行的部分的值，而不是代码全部执行后的值，两者可能会有差异。所以，输入变量的时候，必须非常小心。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 安全的写法</span>
<span class="token keyword">var</span> foo <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>foo<span class="token punctuation">;</span> <span class="token comment">// 危险的写法</span>

exports<span class="token punctuation">.</span><span class="token function-variable function">good</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> a<span class="token punctuation">.</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token string">'good'</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用的是 a.foo 的最新值</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

exports<span class="token punctuation">.</span><span class="token function-variable function">bad</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token string">'bad'</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用的是一个部分加载时的值</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中，如果发生循环加载，<code>require('a').foo</code>的值很可能后面会被改写，改用<code>require('a')</code>会更保险一点。</p> <h3 id="es6-模块的循环加载"><a href="#es6-模块的循环加载" class="header-anchor">#</a> ES6 模块的循环加载</h3> <p>ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用，如果使用<code>import</code>从一个模块加载变量（即<code>import foo from 'foo'</code>），那些变量不会被缓存，而是成为一个指向被加载模块的引用，需要开发者自己保证，真正取值的时候能够取到值。</p> <p>请看下面这个例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>bar<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">let</span> foo <span class="token operator">=</span> <span class="token string">'foo'</span><span class="token punctuation">;</span>

<span class="token comment">// b.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./a'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">let</span> bar <span class="token operator">=</span> <span class="token string">'bar'</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码中，<code>a.mjs</code>加载<code>b.mjs</code>，<code>b.mjs</code>又加载<code>a.mjs</code>，构成循环加载。执行<code>a.mjs</code>，结果如下。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>上面代码中，执行<code>a.mjs</code>以后会报错，<code>foo</code>变量未定义，这是为什么？</p> <p>让我们一行行来看，ES6 循环加载是怎么处理的。首先，执行<code>a.mjs</code>以后，引擎发现它加载了<code>b.mjs</code>，因此会优先执行<code>b.mjs</code>，然后再执行<code>a.mjs</code>。接着，执行<code>b.mjs</code>的时候，已知它从<code>a.mjs</code>输入了<code>foo</code>接口，这时不会去执行<code>a.mjs</code>，而是认为这个接口已经存在了，继续往下执行。执行到第三行<code>console.log(foo)</code>的时候，才发现这个接口根本没定义，因此报错。</p> <p>解决这个问题的方法，就是让<code>b.mjs</code>运行的时候，<code>foo</code>已经有定义了。这可以通过将<code>foo</code>写成函数来解决。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'foo'</span> <span class="token punctuation">}</span>
<span class="token keyword">export</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// b.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./a'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'bar'</span> <span class="token punctuation">}</span>
<span class="token keyword">export</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>这时再执行<code>a.mjs</code>就可以得到预期结果。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>这是因为函数具有提升作用，在执行<code>import {bar} from './b'</code>时，函数<code>foo</code>就已经有定义了，所以<code>b.mjs</code>加载的时候不会报错。这也意味着，如果把函数<code>foo</code>改写成函数表达式，也会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">foo</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token string">'foo'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码的第四行，改成了函数表达式，就不具有提升作用，执行就会报错。</p> <p>我们再来看 ES6 模块加载器<a href="https://github.com/ModuleLoader/es6-module-loader/blob/master/docs/circular-references-bindings.md" target="_blank" rel="noopener noreferrer">SystemJS<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>给出的一个例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// even.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> odd <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./odd'</span>
<span class="token keyword">export</span> <span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">even</span><span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  counter<span class="token operator">++</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> n <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">odd</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// odd.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> even <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./even'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">odd</span><span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> n <span class="token operator">!==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">even</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>上面代码中，<code>even.js</code>里面的函数<code>even</code>有一个参数<code>n</code>，只要不等于 0，就会减去 1，传入加载的<code>odd()</code>。<code>odd.js</code>也会做类似操作。</p> <p>运行上面这段代码，结果如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>$ babel<span class="token operator">-</span>node
<span class="token operator">&gt;</span> <span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> m <span class="token keyword">from</span> <span class="token string">'./even.js'</span><span class="token punctuation">;</span>
<span class="token operator">&gt;</span> m<span class="token punctuation">.</span><span class="token function">even</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token boolean">true</span>
<span class="token operator">&gt;</span> m<span class="token punctuation">.</span>counter
<span class="token number">6</span>
<span class="token operator">&gt;</span> m<span class="token punctuation">.</span><span class="token function">even</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span>
<span class="token boolean">true</span>
<span class="token operator">&gt;</span> m<span class="token punctuation">.</span>counter
<span class="token number">17</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中，参数<code>n</code>从 10 变为 0 的过程中，<code>even()</code>一共会执行 6 次，所以变量<code>counter</code>等于 6。第二次调用<code>even()</code>时，参数<code>n</code>从 20 变为 0，<code>even()</code>一共会执行 11 次，加上前面的 6 次，所以变量<code>counter</code>等于 17。</p> <p>这个例子要是改写成 CommonJS，就根本无法执行，会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// even.js</span>
<span class="token keyword">var</span> odd <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./odd'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
exports<span class="token punctuation">.</span>counter <span class="token operator">=</span> counter<span class="token punctuation">;</span>
exports<span class="token punctuation">.</span><span class="token function-variable function">even</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  counter<span class="token operator">++</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> n <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">odd</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// odd.js</span>
<span class="token keyword">var</span> even <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./even'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>even<span class="token punctuation">;</span>
module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> n <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">even</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>上面代码中，<code>even.js</code>加载<code>odd.js</code>，而<code>odd.js</code>又去加载<code>even.js</code>，形成“循环加载”。这时，执行引擎就会输出<code>even.js</code>已经执行的部分（不存在任何结果），所以在<code>odd.js</code>之中，变量<code>even</code>等于<code>undefined</code>，等到后面调用<code>even(n - 1)</code>就会报错。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ node
<span class="token operator">&gt;</span> var m <span class="token operator">=</span> require<span class="token punctuation">(</span><span class="token string">'./even'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&gt;</span> m.even<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>
TypeError: even is not a <span class="token keyword">function</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></div></div> <div class="page-slot page-slot-bottom"><!-- 横向自适应 -->
      <ins class="adsbygoogle"
          style="display:block"
          data-ad-client="ca-pub-7828333725993554"
          data-ad-slot="6620245489"
          data-ad-format="auto"
          data-full-width-responsive="true"></ins>
      <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
      </script></div> <div class="page-edit"><!----> <div class="tags"><a href="/tags/?tag=ES6" title="标签">#ES6</a></div> <!----></div> <div class="page-nav-wapper"><div class="page-nav-centre-wrap"><a href="/pages/efe2fb04eb8ac5fb/" class="page-nav-centre page-nav-centre-prev"><div class="tooltip">Module 的语法</div></a> <a href="/pages/984bf549204bb266/" class="page-nav-centre page-nav-centre-next"><div class="tooltip">编程风格</div></a></div> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/pages/efe2fb04eb8ac5fb/" class="prev">Module 的语法</a></span> <span class="next"><a href="/pages/984bf549204bb266/">编程风格</a>→
      </span></p></div></div></div> <div class="article-list"><div class="article-title"><a href="/archives/" class="iconfont icon-bi">最近更新</a></div> <div class="article-wrapper"><dl><dd>01</dd> <dt><a href="/pages/dcedab/"><div>Nginx配置https转http</div></a> <span>12-10</span></dt></dl><dl><dd>02</dd> <dt><a href="/pages/82baa3/"><div>使用State Hook</div></a> <span>04-06</span></dt></dl><dl><dd>03</dd> <dt><a href="/pages/72710d/"><div>使用Effect Hook</div></a> <span>04-06</span></dt></dl> <dl><dd></dd> <dt><a href="/archives/" class="more">更多文章&gt;</a></dt></dl></div></div></main></div> <div class="footer"><!----> 
  Theme by
  <a href="https://github.com/xugaoyi/vuepress-theme-vdoing" target="_blank" title="本站主题">Vdoing</a> 
    | Copyright © 2021-2022
    <span><div style="width:300px;margin:0 auto; padding:20px 0;"><a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=13019902000398" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;"><img src="" style="float:left;"/><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">冀公网安备 13019902000398号</p></a></div></span></div> <div class="buttons"><div title="返回顶部" class="button blur go-to-top iconfont icon-fanhuidingbu" style="display:none;"></div> <div title="去评论" class="button blur go-to-comment iconfont icon-pinglun" style="display:none;"></div> <div title="主题模式" class="button blur theme-mode-but iconfont icon-zhuti"><ul class="select-box" style="display:none;"><li class="iconfont icon-zidong">
          跟随系统
        </li><li class="iconfont icon-rijianmoshi">
          浅色模式
        </li><li class="iconfont icon-yejianmoshi">
          深色模式
        </li><li class="iconfont icon-yuedu">
          阅读模式
        </li></ul></div></div> <div class="body-bg" style="background:url() center center / cover no-repeat;opacity:0.5;"></div> <!----> <div class="custom-html-window custom-html-window-rb" style="display:;"><div class="custom-wrapper"><i class="close-but">×</i> <div><!-- 固定160*160px -->
      <ins class="adsbygoogle"
          style="display:inline-block;max-width:160px;max-height:160px"
          data-ad-client="ca-pub-7828333725993554"
          data-ad-slot="8377369658"></ins>
      <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
      </script>
      </div></div></div></div><div class="global-ui"></div></div>
    <script src="/assets/js/app.ae5bf724.js" defer></script><script src="/assets/js/2.72b09a26.js" defer></script><script src="/assets/js/3.96877024.js" defer></script><script src="/assets/js/107.7ca5e73a.js" defer></script>
  </body>
</html>